#include <geo\mercatorProjection.h>
#include <io\osmio.h>

#include <iostream>
#include <string>
#include <sstream>
#include <cmath>

coordinates2i 
gpx_generator::transform(coordinates2i c, llcoordinates resolution) {
  
  // determine delta xy of the input data set
  coordinates2d delta;
  delta.x = max.x - min.x;
  delta.y = max.y - min.y;

  // project the reqeusted resolution to xy
  coordinates2d loc00 = mercator<coordinates2d>::convert(llcoordinates(0.0, 0.0));
  coordinates2d loc_res = mercator<coordinates2d>::convert(base_resolution);
  coordinates2d loc_res2 = mercator<coordinates2d>::convert(resolution);

  // compute width and height of the scene 
  coordinates2d scene_size;
  scene_size.x = delta.x / (loc_res.x - loc00.x);
  scene_size.y = delta.y / (loc_res.y - loc00.y);

  coordinates2d scene_size2;
  scene_size2.x = delta.x / (loc_res2.x - loc00.x);
  scene_size2.y = delta.y / (loc_res2.y - loc00.y);

  coordinates2i cout;
  double fx = (scene_size2.x / scene_size.x);
  double fy = (scene_size2.y / scene_size.y);
  fx = fx * (double) c.x;
  fy = fy * (double) c.y;
  cout.x = ROUND(fx);
  cout.y = ROUND(fy);

  return cout;
}

void 
gpx_generator::initialize(osmfile *osm, llcoordinates base_resolution) {
  this->base_resolution = base_resolution;

  base_tracks.clear();

  // get xy  min and max coordinates of input dataset
  min = mercator<coordinates2d>::convert(osm->getMin());
  max = mercator<coordinates2d>::convert(osm->getMax());
  
  // project the reqeusted resolution to xy
  coordinates2d loc00 = mercator<coordinates2d>::convert(llcoordinates(0.0, 0.0));
  coordinates2d loc_res = mercator<coordinates2d>::convert(base_resolution);

  // determine delta xy of the input data set
  delta.x = max.x - min.x;
  delta.y = max.y - min.y;

  // compute width and height of the scene 
  scene_size.x = delta.x / (loc_res.x - loc00.x);
  scene_size.y = delta.y / (loc_res.y - loc00.y);

  // step through all tracks and complete the track to base resolution
  int t = -1;
  for (int trk = 0; trk < osm->size(); trk++) {
    t++;
    // create new track
    base_tracks.push_back(track<coordinates2i>(t));

    // remove empty tracks
    if (t > 0 && base_tracks[t - 1].size() == 0) {
      base_tracks.pop_back();
      t--;
    }

    int size = ((*osm)[trk].size());
    track<llcoordinates> &track_ref = osm->getTrack(trk);

    // pass through selected track
    for (int p = 0; p < size; p++) {
      // project point from track to xy
      coordinates2d c = mercator<coordinates2d>::convert(llcoordinates(track_ref[p].lat, track_ref[p].lng));
      
      // transform the point c by scene size
      coordinates2d c3;
      c3.x = scene_size.x * ((c.x - min.x) / delta.x);
      c3.y = scene_size.y * ((c.y - min.y) / delta.y);

      coordinates2i c2;
      c2.x = ROUND(c3.x);
      c2.y = ROUND(c3.y);

      c2 = transform(c2, base_resolution);

      // use first point 
      if (base_tracks[t].size() == 0) {
        base_tracks[t].addCoordinate(c2);
      }
      else { 
        // if distance between two successive points isgreater then 10 units, create new track
        if (abs(c2.x - base_tracks[t].last().x) > MAX_DISTANCE || abs(c2.y - base_tracks[t].last().y) > MAX_DISTANCE) {
          t++;
          // create new track
          base_tracks.push_back(track<coordinates2i>(t));
          // remove th eprevisou track if it is empty
          if (t > 0 && base_tracks[t - 1].size() == 0) {
            base_tracks.pop_back();
            t--;
          }
          // use actual point as first point of the new track
          base_tracks[t].addCoordinate(c2);
        }

        // if actual point differ from last point in actual track
        else if (c2 != base_tracks[t].last()) {
          coordinates2i c1 = base_tracks[t].last();
          coordinates2i oc2 = c2;

          // MODIFIED BRESENHAM algorithm
          bool steep = abs(c2.y - c1.y) > abs(c2.x - c1.x);
          bool change12 = false;

          if (steep) {
            SWAP(c1.x, c1.y);
            SWAP(c2.x, c2.y);
          }

          if (c1.x > c2.x) change12 = true;

          int deltax = abs(c2.x - c1.x);
          int deltay = abs(c2.y - c1.y);
          int error = deltax / 2;

          int ystep;
          int xstep;
          int y = c1.y;
          if (c1.y < c2.y) ystep = 1; else ystep = -1;
          if (c1.x < c2.x) xstep = 1; else xstep = -1;

          base_tracks[t].pop_last();

          for (int x = c1.x; change12 ? x >= c2.x : x <= c2.x; x += xstep) {
            coordinates2i c12;
            if (steep) {
              c12.x = y;
              c12.y = x;
              base_tracks[t].addCoordinate(c12);
            }
            else {
              c12.x = x;
              c12.y = y;
              base_tracks[t].addCoordinate(c12);
            }
            error = error - deltay;
            if (error < 0) {
              y += ystep;
              error += deltax;
            }
          }
        }
      }
    }
  }
};

void 
gpx_generator::generate(llcoordinates resolution) {

  gen_tracks.clear();
  
  // step through all tracks and complete the track to requsted resolution
  int t = -1;
  for (int trk = 0; trk < base_tracks.size(); trk++) {
    t++;

    // create new track
    gen_tracks.push_back(track<coordinates2i>(t));

    // remove empty tracks
    if (t > 0 && base_tracks[t - 1].size() == 0) {
      gen_tracks.pop_back();
      t--;
    }

    // select track
    int size = base_tracks[trk].size();
    track<coordinates2i> &track_ref = base_tracks[trk];

    // pass through selected track
    for (int p = 0; p < size; p++) {
      coordinates2i c = track_ref[p];
      c = transform(c, resolution);

      // use first point 
      if (gen_tracks[t].size() == 0) {
        gen_tracks[t].addCoordinate(c);
        //std::cout << c.x << "," << c.y << std::endl;
      }
      else { 
        // if distance between two successive points isgreater then 10 units, create new track
        if (abs(c.x - gen_tracks[t].last().x) > MAX_DISTANCE || abs(c.y - gen_tracks[t].last().y) > MAX_DISTANCE) {
          t++;
          // create new track
          gen_tracks.push_back(track<coordinates2i>(t));
          // remove th eprevisou track if it is empty
          if (t > 0 && gen_tracks[t - 1].size() == 0) {
            gen_tracks.pop_back();
            t--;
          }
          // use actual point as first point of the new track
          gen_tracks[t].addCoordinate(c);
          //std::cout << c.x << "," << c.y << std::endl;
        }

        // if actual point differ from last point in actual track
        else if (c != gen_tracks[t].last()) {
          coordinates2i c1 = gen_tracks[t].last();
          coordinates2i oc2 = c;

          // MODIFIED BRESENHAM algorithm
          bool steep = abs(c.y - c1.y) > abs(c.x - c1.x);
          bool change12 = false;

          if (steep) {
            SWAP(c1.x, c1.y);
            SWAP(c.x, c.y);
          }

          if (c1.x > c.x) change12 = true;

          int deltax = abs(c.x - c1.x);
          int deltay = abs(c.y - c1.y);
          int error = deltax / 2;

          int ystep;
          int xstep;
          int y = c1.y;
          if (c1.y < c.y) ystep = 1; else ystep = -1;
          if (c1.x < c.x) xstep = 1; else xstep = -1;

          gen_tracks[t].pop_last();

          for (int x = c1.x; change12 ? x >= c.x : x <= c.x; x += xstep) {
            coordinates2i c12;
            if (steep) {
              c12.x = y;
              c12.y = x;
              gen_tracks[t].addCoordinate(c12);
              //std::cout << c12.x << "," << c12.y << std::endl;
            }
            else {
              c12.x = x;
              c12.y = y;
              gen_tracks[t].addCoordinate(c12);
              //std::cout << c12.x << "," << c12.y << std::endl;
            }
            error = error - deltay;
            if (error < 0) {
              y += ystep;
              error += deltax;
            }
          }
        }
      }
    }
  }
  
  // set tracks iterator to begin
  start();
};

trajectory *
gpx_generator::next() {

  if (tracks_iter != gen_tracks.end()) {
    int size = tracks_iter->size();

    if (size < 1) {
      tracks_iter++;
      return NULL;
    }

    trajectory *t = new trajectory();
    std::stringstream directions("");
    coordinates2i c1, c2;

    for (int i = 0; i < (size - 1); i++) {
      c1.x = (*tracks_iter)[i + 0].x;
      c1.y = (*tracks_iter)[i + 0].y;

      if (i == 0) t->setStartCell(c1);

      c2.x = (*tracks_iter)[i + 1].x;
      c2.y = (*tracks_iter)[i + 1].y;

      if (c1 != c2) {
        unsigned short direction = c1.direction(c2);
        if (direction < 0 || direction > 7) {
          std::cout << " ERROR IN DIRECTION :" << direction;
        }
        directions << direction;
      }
    }
    t->setDirections(directions.str());
    tracks_iter++;
    if (directions.str().length() != 0) return t;
  }

  return NULL;
};

bool 
gpx_generator::end() {
  return tracks_iter == gen_tracks.end();
}

gpx_generator::gpx_generator() {
  tracks_iter = gen_tracks.end();
}

gpx_generator::~gpx_generator() {
  gen_tracks.clear();
}